<!doctype html>
<!--
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
  <meta charset="utf-8">
  <script src="../../node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
  <script src="wct-browser-config.js"></script>
  <script src="../../node_modules/wct-browser-legacy/browser.js"></script>
  <script type="module" src="../../polymer-legacy.js"></script>
  <script type="module" src="./templatize-elements.js"></script>
</head>
<body>

<template id="standalone"><div prop="[[prop]]">[[text]]</div></template>

<script type="module">
import './templatize-elements.js';
import { templatize } from '../../lib/utils/templatize.js';

suite('templatize basic', function() {

  let host;
  let childA;

  setup(function() {
    host = document.createElement('x-host');
    document.body.appendChild(host);
    host.$.templatizeA.go(true);
    childA = host.shadowRoot.querySelector('#childA');
    assert.ok(childA);
  });

  teardown(function() {
    document.body.removeChild(host);
  });

  test('stamped with initial data', function() {
    assert.equal(childA.outerProp, 'outerProp');
    assert.equal(childA.outerObj, host.outerObj);
    assert.equal(childA.outerObjProp, 'outerObj.prop');
    assert.equal(childA.prop, 'prop-a');
    assert.equal(childA.obj, host.objA);
    assert.equal(childA.objProp, 'objA.prop');
  });

  test('host properties propagated in', function() {
    host.outerProp = 'outerProp++';
    assert.equal(childA.outerProp, 'outerProp++');
    host.outerObj = { prop: 'outerObj++.prop' };
    assert.equal(childA.outerObj, host.outerObj);
    assert.equal(childA.outerObjProp, 'outerObj++.prop');
  });

  test('host paths propagated in', function() {
    sinon.spy(childA, 'outerObjChanged');
    host.set('outerObj.prop', 'outerObj.prop++');
    assert.equal(childA.outerObjProp, 'outerObj.prop++');
    assert.isTrue(childA.outerObjChanged.calledOnce);
    assert.equal(childA.outerObjChanged.getCall(0).args[0].path, 'outerObj.prop');
    assert.equal(childA.outerObjChanged.getCall(0).args[0].value, 'outerObj.prop++');
  });

  test('host properties propagated out', function() {
    childA.outerProp = 'outerProp++';
    assert.equal(host.outerProp, 'outerProp++');
    childA.outerObj = { prop: 'outerObj++.prop' };
    assert.equal(host.outerObj.prop, 'outerObj++.prop');
  });

  test('host paths propagated out', function() {
    sinon.spy(host, 'outerObjChanged');
    childA.set('outerObj.prop', 'outerObj.prop++');
    assert.equal(host.outerObj.prop, 'outerObj.prop++');
    assert.isTrue(host.outerObjChanged.calledOnce);
    assert.equal(host.outerObjChanged.getCall(0).args[0].path, 'outerObj.prop');
    assert.equal(host.outerObjChanged.getCall(0).args[0].value, 'outerObj.prop++');
  });

  test('instance properties propagated in', function() {
    host.propA = 'prop-a++';
    assert.equal(childA.prop, 'prop-a++');
    host.objA = { prop: 'objA++.prop' };
    assert.equal(childA.obj, host.objA);
    assert.equal(childA.objProp, 'objA++.prop');
  });

  test('instance paths propagated in', function() {
    sinon.spy(childA, 'objChanged');
    host.set('objA.prop', 'objA.prop++');
    assert.equal(childA.obj.prop, 'objA.prop++');
    assert.isTrue(childA.objChanged.calledOnce);
    assert.equal(childA.objChanged.getCall(0).args[0].path, 'obj.prop');
    assert.equal(childA.objChanged.getCall(0).args[0].value, 'objA.prop++');
  });

  test('instance properties propagated out', function() {
    childA.prop = 'prop-a++';
    assert.equal(host.propA, 'prop-a++');
    childA.obj = { prop: 'objA++.prop' };
    assert.equal(host.objA.prop, 'objA++.prop');
  });

  test('instance paths propagated out', function() {
    sinon.spy(host, 'objAChanged');
    childA.set('obj.prop', 'objA.prop++');
    assert.equal(host.objA.prop, 'objA.prop++');
    assert.isTrue(host.objAChanged.calledOnce);
    assert.equal(host.objAChanged.getCall(0).args[0].path, 'objA.prop');
    assert.equal(host.objAChanged.getCall(0).args[0].value, 'objA.prop++');
  });

  test('outer & inner props conflict', function() {
    assert.equal(childA.conflict, 'bar');
    host.set('outerInnerConflict.prop', 'foo');
    assert.equal(childA.conflict, 'bar');
  });

  test('inline computed functions', function() {
    assert.equal(childA.computedFromLiteral, '33-prop-a');
  });

  test('ensure literals are not forwarded to templates', function() {
    assert.notOk(host._hasAccessor(33));
    assert.notOk(Object.getOwnPropertyDescriptor(Object.getPrototypeOf(host), 33));
  });

  test('ensure undefined is not set on templatizee', function() {
    assert.equal(host.$.templatizeA[undefined], undefined);
  });

  test('able to add gesture listeners', function() {
    assert.equal(host.handleTap.callCount, 0);
    childA.click();
    assert.equal(host.handleTap.callCount, 1);
  });

  test('listener event decorated with model', function() {
    host.outerProp = 'foo';
    childA.click();
    assert.equal(host.handleTap.callCount, 1);
    let event = host.handleTap.firstCall.args[0];
    assert.instanceOf(event, Event);
    assert.ok(event.model);
    assert.equal(event.model.outerProp, 'foo');
    host.outerProp = 'bar';
    assert.equal(event.model.outerProp, 'bar');
    assert.equal(event.model.parentModel, host);
  });

});


suite('templatize client with/without props', function() {

  let host;
  let childB;

  setup(function() {
    host = document.createElement('x-host');
    document.body.appendChild(host);
  });

  teardown(function() {
    document.body.removeChild(host);
  });

  test('templatize with no dataHost', function() {
    host.shadowRoot.querySelector('[name=templatizeB]').go(true);
    childB = host.shadowRoot.querySelector('#childB');
    assert.ok(childB);
    assert.equal(childB.computedFromLiteral, '33-prop-a');
  });

  test('templatize with no props', function() {
    host.shadowRoot.querySelector('[name=templatizeB]').go(false);
    childB = host.shadowRoot.querySelector('#childB');
    assert.ok(childB);
    assert.isUndefined(childB.computedFromLiteral);
  });

  test('templatize with no props, instance manually flushes', function() {
    let templatizeA = host.shadowRoot.querySelector('[id=templatizeA]');
    templatizeA.go(false);
    let childA = host.shadowRoot.querySelector('#childA');
    assert.ok(childA);
    sinon.spy(childA, 'objChanged');
    assert.isUndefined(childA.obj);
    let o = {foo: true};
    templatizeA.instance._setPendingProperty('obj', o);
    templatizeA.instance._flushProperties();
    assert.equal(childA.obj, o);
    assert.isTrue(childA.objChanged.calledOnce);
  });

});

suite('templatizer behavior', function() {

  let host;
  let childC;

  setup(function() {
    host = document.createElement('x-host');
    document.body.appendChild(host);
    host.$.templatizeC.go(true);
    childC = host.shadowRoot.querySelector('#childC');
    assert.ok(childC);
  });

  teardown(function() {
    document.body.removeChild(host);
  });

  test('stamped with initial data', function() {
    assert.equal(childC.outerProp, 'outerProp');
    assert.equal(childC.outerObj, host.outerObj);
    assert.equal(childC.outerObjProp, 'outerObj.prop');
    assert.equal(childC.prop, 'prop-a');
    assert.equal(childC.obj, host.objA);
    assert.equal(childC.objProp, 'objA.prop');
  });

  test('host properties propagated in', function() {
    host.outerProp = 'outerProp++';
    assert.equal(childC.outerProp, 'outerProp++');
    host.outerObj = { prop: 'outerObj++.prop' };
    assert.equal(childC.outerObj, host.outerObj);
    assert.equal(childC.outerObjProp, 'outerObj++.prop');
  });

  test('host paths propagated in', function() {
    sinon.spy(childC, 'outerObjChanged');
    host.set('outerObj.prop', 'outerObj.prop++');
    assert.equal(childC.outerObjProp, 'outerObj.prop++');
    assert.isTrue(childC.outerObjChanged.calledOnce);
    assert.equal(childC.outerObjChanged.getCall(0).args[0].path, 'outerObj.prop');
    assert.equal(childC.outerObjChanged.getCall(0).args[0].value, 'outerObj.prop++');
  });

  test('host properties propagated out', function() {
    childC.outerProp = 'outerProp++';
    assert.equal(host.outerProp, 'outerProp++');
    childC.outerObj = { prop: 'outerObj++.prop' };
    assert.equal(host.outerObj.prop, 'outerObj++.prop');
  });

  test('host paths propagated out', function() {
    sinon.spy(host, 'outerObjChanged');
    childC.set('outerObj.prop', 'outerObj.prop++');
    assert.equal(host.outerObj.prop, 'outerObj.prop++');
    assert.isTrue(host.outerObjChanged.calledOnce);
    assert.equal(host.outerObjChanged.getCall(0).args[0].path, 'outerObj.prop');
    assert.equal(host.outerObjChanged.getCall(0).args[0].value, 'outerObj.prop++');
  });

  test('instance properties propagated in', function() {
    host.propA = 'prop-a++';
    assert.equal(childC.prop, 'prop-a++');
    host.objA = { prop: 'objA++.prop' };
    assert.equal(childC.obj, host.objA);
    assert.equal(childC.objProp, 'objA++.prop');
  });

  test('instance paths propagated in', function() {
    sinon.spy(childC, 'objChanged');
    host.set('objA.prop', 'objA.prop++');
    assert.equal(childC.obj.prop, 'objA.prop++');
    assert.isTrue(childC.objChanged.calledOnce);
    assert.equal(childC.objChanged.getCall(0).args[0].path, 'obj.prop');
    assert.equal(childC.objChanged.getCall(0).args[0].value, 'objA.prop++');
  });

  test('instance properties propagated out', function() {
    childC.prop = 'prop-a++';
    assert.equal(host.propA, 'prop-a++');
    childC.obj = { prop: 'objA++.prop' };
    assert.equal(host.objA.prop, 'objA++.prop');
  });

  test('instance paths propagated out', function() {
    sinon.spy(host, 'objAChanged');
    childC.set('obj.prop', 'objA.prop++');
    assert.equal(host.objA.prop, 'objA.prop++');
    assert.isTrue(host.objAChanged.calledOnce);
    assert.equal(host.objAChanged.getCall(0).args[0].path, 'objA.prop');
    assert.equal(host.objAChanged.getCall(0).args[0].value, 'objA.prop++');
  });

  test('outer & inner props conflict', function() {
    assert.equal(childC.conflict, 'bar');
    host.set('outerInnerConflict.prop', 'foo');
    assert.equal(childC.conflict, 'bar');
  });

  test('inline computed functions', function() {
    assert.equal(childC.computedFromLiteral, '33-prop-a');
  });

  test('ensure literals are not forwarded to templates', function() {
    assert.notOk(host._hasAccessor(33));
    assert.notOk(Object.getOwnPropertyDescriptor(Object.getPrototypeOf(host), 33));
  });

  test('ensure undefined is not set on templatizee', function() {
    assert.equal(host.$.templatizeA[undefined], undefined);
  });

  test('able to add gesture listeners', function() {
    assert.equal(host.handleTap.callCount, 0);
    childC.click();
    assert.equal(host.handleTap.callCount, 1);
  });

  test('listener event decorated with model', function() {
    host.outerProp = 'foo';
    childC.click();
    assert.equal(host.handleTap.callCount, 1);
    let event = host.handleTap.firstCall.args[0];
    assert.instanceOf(event, Event);
    assert.ok(event.model);
    assert.equal(event.model.outerProp, 'foo');
    host.outerProp = 'bar';
    assert.equal(event.model.outerProp, 'bar');
  });

});

suite('templatize with no host', function() {

  test('stamped with initial data', function() {
    const template = document.getElementById('standalone').cloneNode(true);
    const Template = templatize(template);
    const inst = new Template({prop: 'prop', text: 'text'});
    const div = inst.root.firstChild;
    assert.equal(div.prop, 'prop');
    assert.equal(div.textContent, 'text');
  });

  test('stamped with no initial data', function() {
    const template = document.getElementById('standalone').cloneNode(true);
    const Template = templatize(template);
    const inst = new Template();
    const div = inst.root.firstChild;
    assert.equal(div.prop, undefined);
    assert.equal(div.textContent.trim(), '');
    inst.setProperties({prop: 'prop', text: 'text'});
    assert.equal(div.prop, 'prop');
    assert.equal(div.textContent, 'text');
  });

  test('notifies path data changes', function() {
    const template = document.getElementById('standalone').cloneNode(true);
    const Template = templatize(template);
    const inst = new Template();
    const div = inst.root.firstChild;
    inst.setProperties({prop: {foo: true}});
    assert.equal(div.prop.foo, true);
    inst.set('prop.foo', false);
    assert.equal(div.prop.foo, false);
  });

});
</script>
</body>
</html>
